package microcredit.gui.util;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.swing.AbstractButton;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.JTextComponent;

import microcredit.gui.table.BeanTable;
import microcredit.model.util.ModelUtils;

import org.jdesktop.swingx.JXDatePicker;

public class UIStateCapture {

	private final Component c;
	private List<ActionListener> actionListeners = new ArrayList<ActionListener>();
	
	private boolean dirty;
	
	private final Set<JComponent> components;
	
	public UIStateCapture(Component c) {
		this.c = c;
		this.components = new HashSet<JComponent>();
		UIUtil.iterate(c, new ComponentIterator<Void>() {
			@Override
			public boolean iterateChildren(Component c) {
				return !(c instanceof JXDatePicker);
			}
			@Override
			public Void iterate(Component c) {
				enable(c);
				return null;
			}
		});
	}
	
	public void addActionListener(ActionListener l) {
		actionListeners.add(l);
	}
	
	public void removeActionListener(ActionListener l) {
		actionListeners.remove(l);
	}
	
	private void enable(Component c) {
		if (c instanceof JComponent) {
			saveState((JComponent)c);
		}
		
		if (c instanceof JTextComponent) {
			DocumentListener listener = new DocumentListenerImpl((JTextComponent)c);
			((JTextComponent) c).getDocument().addDocumentListener(listener);
			((JTextComponent) c).putClientProperty("uiStateDocumentListener", listener);
		} else if (c instanceof AbstractButton) {
			if (!(c instanceof JButton)) {
				((AbstractButton) c).addActionListener(actionListener);
			}
		} else if (c instanceof JComboBox) {
			((JComboBox) c).addItemListener(itemListener);
		} else if (c instanceof BeanTable<?>) {
			((BeanTable<?>)c).addChangeListener(changeListener);
		} else if (c instanceof JXDatePicker) {
			((JXDatePicker) c).addActionListener(actionListener);
		}
	}
	
	private static final String VALUE_STATE = "valueState";
	
	private void saveState(JComponent c) {
		c.putClientProperty(VALUE_STATE, getValue(c));
	}
	
	private Object getValue(Component c) {
		if (c instanceof JTextComponent) {
			return ((JTextComponent) c).getText();
		} else if (c instanceof JComboBox) {
			return ((JComboBox) c).getSelectedItem();
		} else if (c instanceof JXDatePicker) {
			return ((JXDatePicker) c).getDate();
		}
		return NO_VALUE;
	}
	
	private static final Object NO_VALUE = new Object();
	
	private void disable(Component c) {
		if (c instanceof JTextComponent) {
			DocumentListener listener = (DocumentListener)((JTextComponent) c).getClientProperty("uiStateDocumentListener");
			if (listener != null) {
				((JTextComponent) c).getDocument().removeDocumentListener(listener);
			}
		} else if (c instanceof AbstractButton) {
			if (!(c instanceof JButton)) {
				((AbstractButton) c).removeActionListener(actionListener);
			}
		} else if (c instanceof JComboBox) {
			((JComboBox) c).removeItemListener(itemListener);
		} else if (c instanceof BeanTable<?>) {
			((BeanTable<?>)c).removeChangeListener(changeListener);
		} else if (c instanceof JXDatePicker) {
			((JXDatePicker) c).removeActionListener(actionListener);
		}
	}
	
	public boolean isDirty() {
		for (JComponent c : components) {
			Object newValue = getValue(c);
			Object oldValue = ((JComponent) c).getClientProperty(VALUE_STATE);
			if (oldValue == NO_VALUE || !ModelUtils.equals(newValue, oldValue)) {
				return true;
			}
		}
		return false;
	}
	
	private void change(Component c) {
		if (c != null && c instanceof JComponent && components.isEmpty()) {
			Object newValue = getValue(c);
			Object oldValue = ((JComponent) c).getClientProperty(VALUE_STATE);
			if (oldValue != NO_VALUE 
					&& ModelUtils.equals(newValue, oldValue)
					&& !isDirty()) {
				return;
			}
		}
		components.add((JComponent)c);
		
		ActionEvent evt = new ActionEvent(c, 0, null);
		for (ActionListener listener : actionListeners) {
			listener.actionPerformed(evt);
		}
	}
	
	public static UIStateCapture capture(Component c) {
		return new UIStateCapture(c);
	}
	
	public static void destroy(final UIStateCapture capture) {
		UIUtil.iterate(capture.c, new ComponentIterator<Void>() {
			@Override
			public boolean iterateChildren(Component c) {
				return true;
			}
			@Override
			public Void iterate(Component c) {
				capture.disable(c);
				return null;
			}
		});
	}
	
	private ActionListener actionListener = new ActionListener() {
		@Override
		public void actionPerformed(ActionEvent e) {
			change((Component)e.getSource());
		}
	};
	
	private ItemListener itemListener = new ItemListener() {
		@Override
		public void itemStateChanged(ItemEvent e) {
			change((Component)e.getSource());
		}
	};
	
	private BeanTable.ChangeListener changeListener = new BeanTable.ChangeListener() {
		public void changed(BeanTable<?> table) {
			change(table);
		};
	};

	private final class DocumentListenerImpl implements DocumentListener {
		
		private final JTextComponent textComponent;
		
		public DocumentListenerImpl(JTextComponent c) {
			this.textComponent = c;
		}
		
		@Override
		public void removeUpdate(DocumentEvent e) {
			change(textComponent);
		}

		@Override
		public void insertUpdate(DocumentEvent e) {
			change(textComponent);
		}

		@Override
		public void changedUpdate(DocumentEvent e) {
			change(textComponent);
		}
	}
	
    
}
